package com.synnapps.carouselview;

import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.Rect;

import ohos.app.Context;

import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;

import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;

import ohos.multimodalinput.event.TouchEvent;

import java.io.IOException;

import java.math.BigDecimal;

import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

/**
 * 自定义轮播图组件
 */
public class CarouselView extends StackLayout {
    private static final int DEFAULT_GRAVITY = LayoutAlignment.HORIZONTAL_CENTER | LayoutAlignment.BOTTOM;
    private static final int DEFAULT_SLIDE_INTERVAL = 3500;
    private static final int DEFAULT_SLIDE_VELOCITY = 400;
    private static final int DEFAULT_INDICATOR_VISIBILITY = VISIBLE;
    private static final int LAYOUT_CONFIG = ComponentContainer.LayoutConfig.MATCH_CONTENT;
    private CarouselViewPager containerViewPager;
    private PageSliderIndicator pageIndicator;
    private ViewListener mViewListener = null;
    private ImageListener mImageListener = null;
    private ImageClickListener imageClickListener = null;
    private Timer swipeTimer;
    private SwipeTask swipeTask;
    private ArrayList<Component> components = new ArrayList<>();
    private PageTransformer mPageTransformer;
    private int pageTransformerType;
    private int fillColor;
    private int pageColor;
    private int strokeColor;
    private int eachWidth;
    private int defaultFillColor;
    private int defaultPageColor;
    private int defaultStrokeColor;
    private int mPageCount;
    private int slideInterval = DEFAULT_SLIDE_INTERVAL;
    private int mIndicatorGravity = DEFAULT_GRAVITY;
    private int indicatorMarginVertical;
    private int indicatorMarginHorizontal;
    private int pageTransformInterval = DEFAULT_SLIDE_VELOCITY;
    private int indicatorVisibility = DEFAULT_INDICATOR_VISIBILITY;
    private int previousState;
    private float radius;
    private float strokeWidth;
    private boolean autoPlay;
    private boolean disableAutoPlayOnUserInteraction;
    private boolean animateOnBoundary = true;
    private ClickedListener pagerListener = new ClickedListener() {
        @Override
        public void onClick(Component component) {
            int width = component.getWidth();
            int count = containerViewPager.getProvider().getCount();
            if (count > 0) {
                eachWidth = width / count;
                component.setTouchEventListener(touchEventListener);
            }
        }
    };
    private TouchEventListener touchEventListener = new TouchEventListener() {
        @Override
        public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
            switch (touchEvent.getAction()) {
                case TouchEvent.PRIMARY_POINT_DOWN:
                    float oldX = touchEvent.getPointerPosition(0).getX();
                    int index = (int) oldX / eachWidth;
                    pageIndicator.setSelected(index);
                    containerViewPager.setCurrentPage(index);
                    break;
                case TouchEvent.PRIMARY_POINT_UP:
                    break;
            }
            return true;
        }
    };
    private PageSlider.PageChangedListener carouselOnPageChangeListener = new PageSlider.PageChangedListener() {
        @Override
        public void onPageSliding(int position, float positionOffset, int positionOffsetPixels) {
            if (mPageTransformer != null) {
                onPageScrolled(position, positionOffset, positionOffsetPixels);
            }
        }

        @Override
        public void onPageSlideStateChanged(int state) {
            if (previousState == PageSlider.SLIDING_STATE_DRAGGING
                    && state == PageSlider.SLIDING_STATE_SETTLING) {
                if (disableAutoPlayOnUserInteraction) {
                    pauseCarousel();
                } else {
                    playCarousel();
                }
            }
            previousState = state;
        }

        @Override
        public void onPageChosen(int position) {
        }
    };

    /**
     * 构造方法
     *
     * @param context 上下文
     */
    public CarouselView(Context context) {
        this(context, null);
    }

    /**
     * 构造方法
     *
     * @param context 上下文
     * @param attrSet 资源集
     */
    public CarouselView(Context context, AttrSet attrSet) {
        this(context, attrSet, null);
    }

    /**
     * 构造方法
     *
     * @param context   上下文
     * @param attrSet   资源集
     * @param styleName 自定义样式
     */
    public CarouselView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        if (attrSet != null) {
            initView(context, attrSet);
        }
    }

    private void initView(Context context, AttrSet attrs) {
        Component component = LayoutScatter.getInstance(context)
                .parse(ResourceTable.Layout_view_carousel, this, true);
        containerViewPager = (CarouselViewPager) component.findComponentById(ResourceTable.Id_containerViewPager);
        pageIndicator = (PageSliderIndicator) component.findComponentById(ResourceTable.Id_pageIndicator);
        pageIndicator.setClickedListener(pagerListener);
        containerViewPager.addPageChangedListener(carouselOnPageChangeListener);
        indicatorMarginVertical = AttrValue.getInteger(attrs, "indicatorMarginVertical",
                AttrValue.getDimension(context, ResourceTable.Float_default_indicator_margin_vertical));
        indicatorMarginHorizontal = AttrValue.getInteger(attrs, "indicatorMarginHorizontal",
                AttrValue.getDimension(context, ResourceTable.Float_default_indicator_margin_horizontal));
        setPageTransformInterval(AttrValue.getInteger(attrs, "pageTransformInterval", DEFAULT_SLIDE_VELOCITY));
        setSlideInterval(AttrValue.getInteger(attrs, "slideInterval", DEFAULT_SLIDE_INTERVAL));
        setIndicatorGravity(AttrValue.getInteger(attrs, "indicatorGravity",
                LayoutAlignment.BOTTOM | LayoutAlignment.HORIZONTAL_CENTER));
        setAutoPlay(AttrValue.getBoolean(attrs, "autoPlay", true));
        setDisableAutoPlayOnUserInteraction(AttrValue.getBoolean(attrs, "disableAutoPlayOnUserInteraction",
                false));
        setAnimateOnBoundary(AttrValue.getBoolean(attrs, "animateOnBoundary", true));
        pageTransformerType = AttrValue.getInteger(attrs, "pageTransformer", CarouselViewTransformer.DEFAULT);
        indicatorVisibility = AttrValue.getInteger(attrs, "indicatorVisibility",
                CarouselView.DEFAULT_INDICATOR_VISIBILITY);
        setIndicatorVisibility(indicatorVisibility);
        if (indicatorVisibility == Component.VISIBLE) {
            try {
                defaultFillColor = getResourceManager()
                        .getElement(ResourceTable.String_default_circle_indicator_fill_color).getColor();
                defaultPageColor = getResourceManager()
                        .getElement(ResourceTable.String_default_circle_indicator_transparent).getColor();
                defaultStrokeColor = getResourceManager()
                        .getElement(ResourceTable.String_default_circle_indicator_stroke_color).getColor();
            } catch (IOException | NotExistException | WrongTypeException e) {
                e.printStackTrace();
            }
            fillColor = AttrValue.getColor(attrs, "fillColor", defaultFillColor);
            pageColor = AttrValue.getColor(attrs, "pageColor", defaultPageColor);
            strokeColor = AttrValue.getColor(attrs, "strokeColor", defaultStrokeColor);
            radius = AttrValue.getFloat(attrs, "radius", 0);
            strokeWidth = AttrValue.getFloat(attrs, "strokeWidth", 0);
        }
    }

    /**
     * Get interval for one slide in milliseconds.
     *
     * @return milliseconds
     */
    public int getSlideInterval() {
        return slideInterval;
    }

    /**
     * Set interval for one slide in milliseconds.
     *
     * @param slideInterval integer
     */
    public void setSlideInterval(int slideInterval) {
        this.slideInterval = slideInterval;
        if (containerViewPager != null) {
            playCarousel();
        }
    }

    /**
     * Set interval for one slide in milliseconds.
     *
     * @param slideInterval integer
     */
    public void reSetSlideInterval(int slideInterval) {
        setSlideInterval(slideInterval);
        if (containerViewPager != null) {
            playCarousel();
        }
    }

    /**
     * Sets speed at which page will slide from one to another in milliseconds
     *
     * @param transformInterval integer
     */
    public void setPageTransformInterval(int transformInterval) {
        if (pageTransformInterval > 0) {
            this.pageTransformInterval = transformInterval;
        } else {
            this.pageTransformInterval = DEFAULT_SLIDE_VELOCITY;
        }
        containerViewPager.setPageSwitchTime(pageTransformInterval);
    }


    /**
     * Sets whether to animate transition from last position to first or not.
     *
     * @param animateOnBoundary .
     */
    public void setAnimateOnBoundary(boolean animateOnBoundary) {
        this.animateOnBoundary = animateOnBoundary;
    }

    /**
     * 获取自动播放状态
     *
     * @return autoPlay
     */
    public boolean isAutoPlay() {
        return autoPlay;
    }

    /**
     * 设置是否自动播放
     *
     * @param autoPlay 开关
     */
    private void setAutoPlay(boolean autoPlay) {
        this.autoPlay = autoPlay;
    }

    /**
     * 获取交互状态
     *
     * @return 状态
     */
    public boolean isDisableAutoPlayOnUserInteraction() {
        return disableAutoPlayOnUserInteraction;
    }

    /**
     * 设置是否关闭自动/手动滑动交互
     *
     * @param disableAutoPlayOnUserInteraction 开关
     */
    private void setDisableAutoPlayOnUserInteraction(boolean disableAutoPlayOnUserInteraction) {
        this.disableAutoPlayOnUserInteraction = disableAutoPlayOnUserInteraction;
    }

    private void setData() {
        CarouselPagerAdapter carouselPagerAdapter = new CarouselPagerAdapter(getContext());
        containerViewPager.setProvider(carouselPagerAdapter);
        setPageTransformer(new CarouselViewTransformer(pageTransformerType));
        if (getPageCount() > 1) {
            pageIndicator.setViewPager(containerViewPager);
            initPageIndicator();
            containerViewPager.setPageCacheSize(getPageCount());
            playCarousel();
        }
    }

    private void initPageIndicator() {
        ShapeElement normalElement = new ShapeElement();
        normalElement.setShape(ShapeElement.OVAL);
        normalElement.setStroke((int) strokeWidth, RgbColor.fromArgbInt(strokeColor));
        normalElement.setBounds(new Rect(0, 0, (int) radius * 2, (int) radius * 2));
        normalElement.setRgbColor(RgbColor.fromArgbInt(pageColor));
        ShapeElement selectElement = new ShapeElement();
        selectElement.setShape(ShapeElement.OVAL);
        selectElement.setBounds(new Rect(0, 0, (int) radius * 2, (int) radius * 2));
        selectElement.setRgbColor(RgbColor.fromArgbInt(fillColor));
        pageIndicator.setItemElement(normalElement, selectElement);
        pageIndicator.setItemOffset((int) radius);
    }

    private void stopScrollTimer() {
        if (swipeTimer != null) {
            swipeTimer.cancel();
        }
        if (swipeTask != null) {
            swipeTask.cancel();
        }
    }

    private void resetScrollTimer() {
        stopScrollTimer();
        swipeTask = new SwipeTask();
        swipeTimer = new Timer();
    }

    /**
     * Starts auto scrolling if
     */
    public void playCarousel() {
        resetScrollTimer();
        if (autoPlay && slideInterval > 0 && containerViewPager.getProvider() != null
                && containerViewPager.getProvider().getCount() > 1) {
            swipeTimer.schedule(swipeTask, slideInterval, slideInterval);
        }
    }

    /**
     * Pause auto scrolling unless user interacts provided autoPlay is enabled.
     */
    public void pauseCarousel() {
        resetScrollTimer();
    }

    /**
     * Stops auto scrolling.
     */
    public void stopCarousel() {
        this.autoPlay = false;
    }

    private class CarouselPagerAdapter extends PageSliderProvider {
        private Context mContext;

        private CarouselPagerAdapter(Context context) {
            mContext = context;
        }

        @Override
        public int getCount() {
            return getPageCount();
        }

        @Override
        public Object createPageInContainer(ComponentContainer componentContainer, int position) {
            Component objectToReturn;
            if (mImageListener != null) {
                Image imageView = new Image(mContext);
                imageView.setLayoutConfig(new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT));
                imageView.setScaleMode(Image.ScaleMode.CLIP_CENTER);
                objectToReturn = imageView;
                mImageListener.setImageForPosition(position, imageView);
                componentContainer.addComponent(imageView);
            } else if (mViewListener != null) {
                Component view = mViewListener.setViewForPosition(position);
                if (view != null) {
                    objectToReturn = view;
                    componentContainer.addComponent(view);
                } else {
                    throw new MyException("View can not be null for position " + position);
                }
            } else {
                throw new MyException("View must set " + ImageListener.class.getSimpleName());
            }
            components.add(position, objectToReturn);
            return objectToReturn;
        }

        @Override
        public void destroyPageFromContainer(ComponentContainer componentContainer, int position, Object view) {
            componentContainer.removeComponent((Component) view);
        }

        @Override
        public boolean isPageMatchToObject(Component component, Object object) {
            return component == object;
        }

        private class MyException extends RuntimeException {
            MyException(String message) {
                super(message);
            }
        }
    }

    /**
     * 设置动画接口
     *
     * @param pageTransformer 动画接口实现类
     */
    public void setPageTransformer(PageTransformer pageTransformer) {
        this.mPageTransformer = pageTransformer;
    }

    private void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        onPageTransformer(position, positionOffset, positionOffsetPixels);
    }

    private void onPageTransformer(int position, float positionOffset, int positionOffsetPixels) {
        if (positionOffsetPixels > 0) {
            scrollLeft(position, positionOffset);
        } else {
            scrollRight(position, positionOffset);
        }
    }

    private void scrollRight(int position, float positionOffset) {
        if (position - 1 >= 0) {
            Component in = components.get(position - 1);
            mPageTransformer.transformPage(in, new BigDecimal(-1)
                    .add(new BigDecimal(positionOffset)).floatValue());
        }
        Component out = components.get(position);
        mPageTransformer.transformPage(out, positionOffset);
    }

    private void scrollLeft(int position, float positionOffset) {
        Component in = components.get(position + 1);
        Component out = components.get(position);
        mPageTransformer.transformPage(in, new BigDecimal(1)
                .subtract(new BigDecimal(positionOffset)).floatValue());
        mPageTransformer.transformPage(out, new BigDecimal(0)
                .subtract(new BigDecimal(positionOffset)).floatValue());
    }

    private class SwipeTask extends TimerTask {
        /**
         * 定时器
         */
        public void run() {
            EventHandler eventHandler = new EventHandler(EventRunner.getMainEventRunner());
            eventHandler.postTask(new Runnable() {
                @Override
                public void run() {
                    int nextPage = (containerViewPager.getCurrentPage() + 1) % getPageCount();
                    containerViewPager.setCurrentPage(nextPage, nextPage != 0 || animateOnBoundary);
                }
            });
        }
    }

    /**
     * 设置图片视图监听
     *
     * @param mImageListener 图片视图监听
     */
    public void setImageListener(ImageListener mImageListener) {
        this.mImageListener = mImageListener;
    }

    /**
     * 设置自定义视图监听
     *
     * @param mViewListener 自定义视图监听
     */
    public void setViewListener(ViewListener mViewListener) {
        this.mViewListener = mViewListener;
    }

    /**
     * 设置条目点击事件监听
     *
     * @param clickListener 接口
     */
    public void setImageClickListener(ImageClickListener clickListener) {
        this.imageClickListener = clickListener;
        containerViewPager.setImageClickListener(imageClickListener);
    }

    /**
     * 获取item的数量
     *
     * @return 数量
     */
    public int getPageCount() {
        return mPageCount;
    }

    /**
     * 设置item的数量
     *
     * @param mPageCount 数量
     */
    public void setPageCount(int mPageCount) {
        this.mPageCount = mPageCount;
        setData();
    }

    /**
     * 设置监听
     *
     * @param listener 监听器
     */
    public void addOnPageChangeListener(PageSlider.PageChangedListener listener) {
        containerViewPager.addPageChangedListener(listener);
    }

    /**
     * 移除PageChangeListener
     *
     * @param listener 监听器
     */
    public void clearOnPageChangeListeners(PageSlider.PageChangedListener listener) {
        containerViewPager.removePageChangedListener(listener);
    }

    /**
     * 设置当前item
     *
     * @param item index
     */
    public void setCurrentItem(int item) {
        containerViewPager.setCurrentPage(item);
    }

    /**
     * 设置当前item
     *
     * @param item         index
     * @param smoothScroll 是否平滑
     */
    public void setCurrentItem(int item, boolean smoothScroll) {
        containerViewPager.setCurrentPage(item, smoothScroll);
    }

    /**
     * 获取当前item
     *
     * @return 当前item
     */
    public int getCurrentItem() {
        return containerViewPager.getCurrentPage();
    }

    /**
     * 获取指示器显示位置
     *
     * @return mIndicatorGravity
     */
    public int getIndicatorGravity() {
        return mIndicatorGravity;
    }

    /**
     * 设置指示器显示位置
     *
     * @param gravity 参照LayoutAlignment.HORIZONTAL_CENTER | LayoutAlignment.BOTTOM int值
     */
    public void setIndicatorGravity(int gravity) {
        mIndicatorGravity = gravity;
        StackLayout.LayoutConfig layoutConfig = new StackLayout.LayoutConfig(LAYOUT_CONFIG, LAYOUT_CONFIG);
        layoutConfig.alignment = mIndicatorGravity;
        layoutConfig.setMargins(indicatorMarginHorizontal, indicatorMarginVertical,
                indicatorMarginHorizontal, indicatorMarginVertical);
        pageIndicator.setLayoutConfig(layoutConfig);
    }

    /**
     * 设置是否显示指示器
     *
     * @param visibility 开关
     */
    public void setIndicatorVisibility(int visibility) {
        pageIndicator.setVisibility(visibility);
    }

    /**
     * 获取指示器圆点填充颜色
     *
     * @return 填充颜色
     */
    public RgbColor getFillColor() {
        return RgbColor.fromArgbInt(fillColor);
    }

    /**
     * 获取指示器圆点边框颜色
     *
     * @return 边框颜色
     */
    public RgbColor getStrokeColor() {
        return RgbColor.fromArgbInt(strokeColor);
    }

    /**
     * 获取指示器圆点背景颜色
     *
     * @return 背景颜色
     */
    public RgbColor getPageColor() {
        return RgbColor.fromArgbInt(pageColor);
    }

    /**
     * 设置指示器圆点半径
     *
     * @param newRadius 圆点半径
     */
    public void setRadius(float newRadius) {
        radius = newRadius;
        initPageIndicator();
    }

    /**
     * 获取指示器圆点边框宽度
     *
     * @return 边框宽度
     */
    public float getStrokeWidth() {
        return strokeWidth;
    }

    /**
     * 设置指示器圆点填充颜色
     *
     * @param newFillColor 填充颜色
     */
    public void setFillColor(int newFillColor) {
        fillColor = newFillColor;
        initPageIndicator();
    }

    /**
     * 设置指示器圆点边框颜色
     *
     * @param newStrokeColor 边框颜色
     */
    public void setStrokeColor(int newStrokeColor) {
        strokeColor = newStrokeColor;
        initPageIndicator();
    }

    /**
     * 设置指示器圆点背景色
     *
     * @param newPageColor 背景色
     */
    public void setPageColor(int newPageColor) {
        pageColor = newPageColor;
        initPageIndicator();
    }

    /**
     * 获取指示器半径
     *
     * @return 半径
     */
    public float getRadius() {
        return radius;
    }

    /**
     * 设置指示器圆点边框宽度
     *
     * @param newStrokeWidth 宽度
     */
    public void setStrokeWidth(float newStrokeWidth) {
        strokeWidth = newStrokeWidth;
        initPageIndicator();
    }
}
